#include <ttable.h>

#if HAVE_CONFIG_H
# include <config.h>
#endif

#include <eval.h>
#include <position.h>
#include <search.h>
#include <stats.h>
#include <utils.h>

#include <assert.h>
#include <stdlib.h>
#include <stdio.h>
#include <inttypes.h>

#define DEFAULT_TTABLE_SIZE 20  // Mbytes

#ifndef NDEBUG
static int init = 0;
#endif


typedef struct ttable_record_t
{
  hash_key_t tag;
  ttable_entry_t entry;
} ttable_record_t;


hash_key_t zobrist_keys[2][6][64][2];
hash_key_t side_hash_keys[2];

static size_t hash_size;
static hash_key_t hash_mask;
static ttable_record_t* records;


static hash_key_t rand_key()
{
  return random64();
}


void ttable_init()
{
  for (int k = 0; k < 2; k++) {
    side_hash_keys[k] = rand_key();
    for (int s = 0; s < 2; s++)
      for (int p = 0; p < 6; p++)
	for (int sq = 0; sq < 64; sq++)
	  zobrist_keys[s][p][sq][k] = rand_key();
  }

  hash_size = 0;
  hash_mask = 0;
  records = 0;
  assert((init = 1));

  ttable_resize(DEFAULT_TTABLE_SIZE);  
}


int ttable_resize(long mbytes)
{
  assert(init);
  assert(mbytes >= 0);
  
  if (mbytes == 0)
    mbytes = 1;

  size_t target = (mbytes * 1024 * 1024) / sizeof(ttable_record_t);

  size_t n = 1;
  while (2 * n <= target)
    n *= 2;

  void* mem = realloc(records, n * sizeof(ttable_record_t));
  if (!mem)
    return 0;
  
  records = (ttable_record_t*)mem;
  hash_size = n;
  hash_mask = n - 1;

  ttable_reset();

  return 1;
}


void ttable_reset()
{
  assert(init);
  for (size_t i = 0; i < hash_size; i++)
    records[i].entry.depth = 0;
}


void ttable_cleanup()
{
  assert(init);
  free(records);
  records = 0;
}


int ttable_probe(const struct position_t* pos,
		 ttable_entry_t* entry)
{
  assert(pos != 0);
  assert(entry != 0);
  assert(init);

  STAT_REPORT(stat_hash_probe); 
  hash_key_t k = position_get_hash_key(pos) & hash_mask;
  k |= 1;
  if (records[k].tag == position_get_hash_tag(pos)) {
      STAT_REPORT(stat_hash_hit); 
      *entry = records[k].entry;
      return 1;
  }
  --k;
  if (records[k].tag == position_get_hash_tag(pos)) {
      STAT_REPORT(stat_hash_hit); 
      *entry = records[k].entry;
      return 1;
  }

  return 0;
}


void ttable_insert(const struct position_t* pos,
		   const ttable_entry_t* entry)
{
  assert(pos != 0);
  assert(entry != 0);
  assert(init);
  assert(entry->score >= -MATE_VAL(0) && entry->score <= MATE_VAL(0));

  hash_key_t k = position_get_hash_key(pos) & hash_mask;
  int depth = entry->depth;
  k &= ~1;
  if (records[k].entry.depth <= depth) {
    records[k].entry = *entry;
    records[k].tag = position_get_hash_tag(pos);
  }
  ++k;
  records[k].entry = *entry;
  records[k].tag = position_get_hash_tag(pos);
}
